#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author: LiangChao
@email：kevinleong1011@hotmail.com
@desc: 
"""
import inspect
import threading
from contextlib import contextmanager
from types import MethodType, FunctionType, ModuleType
from typing import Union

from makit import fn, Input
from makit.task import RUNNING
from .fixture import TestFixture

from .config import config
from .errors import TestFailedError, TestBlockedError, InvalidProjectError, ManualError, UserInterruptError
from .log import logger
from .project import Project, parse_project_package


class __Makitest:
    _lock = threading.Lock()

    def __init__(self):
        self.projects = []
        self.contexts = []  # 测试上下文
        self.default_context = None

    def __new__(cls, *args, **kwargs):
        with cls._lock:
            if not hasattr(cls, '_instance'):
                cls._instance = object.__new__(cls)
            return cls._instance

    def register_script(self, obj: Union[type, FunctionType, MethodType, ModuleType]):
        project = self.get_project(obj)
        project.scripts.append(obj)

    def script(self, **kwargs):

        def deco(func):
            project = self.get_project(func)
            for k, v in kwargs.items():
                setattr(func, k, v)

            project.scripts.append(func)

            return func

        return deco

    @classmethod
    def manual_confirm(cls, message):
        s = Input.bool(message, default=False)
        if not s:
            raise TestFailedError(f'Test is failed by user confirm manually!')

    @classmethod
    def manual_do(cls, message):
        s = Input.get(message + '，continue? Y/n', default='y')
        if s != 'y':
            raise UserInterruptError(f'User interrupt!')

    def info(self, message, frame_depth=2):
        test = self.current_test()
        _logger = test.logger if test else logger
        _logger.info(message, frame_depth=frame_depth)

    def fail(self, message):
        test = self.current_test()
        _logger = test.logger if test else logger
        _logger.error(message, frame_depth=frame_depth)

    def block(self, message):
        test = self.current_test()
        _logger = test.logger if test else logger
        _logger.block(message, frame_depth=frame_depth)

    def success(self, message, frame_depth=2):
        test = self.current_test()
        _logger = test.logger if test else logger
        _logger.success(message, frame_depth=frame_depth)

    def warning(self, message):
        test = self.current_test()
        if test:
            test.logger.warning(message, frame_depth=2)
            test.warning(message)
        else:
            logger.warning(message, frame_depth=2)

    def todo(self, message):
        """
        Add to do item for current test script.

        :param message:
        :return:
        """
        test = self.current_test()
        if test:
            test.todo(message)

    def confirm(self, message):
        """
        Add confirmation for current test script，
        Then you can show it in your report and ask others to confirm something.

        :param message: confirmation message
        :return:
        """
        test = self.current_test()
        if test:
            test.confirm(message)

    def current_test(self):
        for ctx in self.contexts:
            test = ctx.current_test()
            if test:
                return test
        return None

    def current_project(self):
        thread_id = threading.current_thread().ident
        for project in self.projects:
            for task in project.tasks:
                if task.status == RUNNING and task.id == thread_id:
                    return project
        if self.projects:
            return self.projects[0]

    def get_project(self, target, reload=False):
        if isinstance(target, (type, MethodType, FunctionType)):
            target = inspect.getmodule(target)
        root_package = parse_project_package(target)
        for project in self.projects:
            if project.name == root_package:
                if reload:
                    project.reload()
                return project
        # 如果没有找到，则注册
        if not root_package:
            raise InvalidProjectError(target)
        module = __import__(root_package)
        project = Project(module)
        # project.load_config()
        self.projects.append(project)
        return project

    @contextmanager
    def require(self):
        try:
            yield
        except Exception:
            raise TestBlockedError('')

    def get_config(self, item, default=None):
        info = fn.get_caller_info()
        project = self.current_project() or self.get_project(info.module)
        if not project:
            return config.get(item)
        return project.config.get(item)

    def create_fixture(self):
        """
        创建TestFixture，每个fixture都可以添加任意多个测试，但这些测试必须是同一个工程
        :return:
        """
        caller = fn.get_caller_info()
        project = self.get_project(caller.module)
        fixture = TestFixture(project=project)
        return fixture


mk = __Makitest()
